/**
 *  Copyright (C) 2014 lixlee
 *
 *  This software is provided 'as-is', without any express or implied
 *  warranty.  In no event will the authors be held liable for any damages
 *  arising from the use of this software.
 *
 *  Permission is granted to anyone to use this software for any purpose,
 *  including commercial applications, and to alter it and redistribute it
 *  freely, subject to the following restrictions:
 *
 *  1. The origin of this software must not be misrepresented; you must not
 *     claim that you wrote the original software. If you use this software
 *     in a product, an acknowledgment in the product documentation would be
 *     appreciated but is not required.
 *  2. Altered source versions must be plainly marked as such, and must not be
 *     misrepresented as being the original software.
 *  3. This notice may not be removed or altered from any source distribution.
 *
 */

#include "../include/base64.h"

#include <stdlib.h>
#include <memory.h>

#ifndef BOOL
#define BOOL int
#define TRUE 1
#define FALSE 0
#endif

/* The Base 64 Alphabet */
char* g_base64_alphabet_table  =  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

/* The "URL and Filename safe" Base 64 Alphabet */
char* g_base64_alphabet_table2 =  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=";

static char* inner_base64_encode(const char* buf, int len, const char* alphabet_table, BOOL trunc_tail)
{
    char* r = NULL;

    if(!buf || len <=0)
        return NULL;

    int n = 1 + (len + 2) / 3 * 4 + (3 - len % 3) % 3;

    r = (char*) malloc(n);

    if(r == NULL)
        return NULL;

    r[n-1] = '\0';

    char pad_char = alphabet_table[64]; /* '=' */
    char tmp[4] = {0};

    unsigned long long w = 0; /* must at least 24 bit */

    const char* q = buf;
    char* p = r;
    int pad = 0;
    int idx = 0;
    int i;

    while(idx < len)
    {
        if(idx + 3 <= len){
            memcpy(tmp, q, 3);
            idx += 3;
            q += 3;
        }else{
            pad = idx + 3 - len;
            memcpy(tmp, q, len - idx);
            memset(tmp + 3 - pad, 0, pad);
            idx += 3;
        }

        w = (tmp[0] & 0xFF);
        w = (w << 8) | (tmp[1] & 0xFF);
        w = (w << 8) | (tmp[2] & 0xFF);

        p += 3;
        for(i = 0; i < 4; i++){
            *p = alphabet_table[w & 0x3F];
            p--;
            w >>= 6;
        }
        p += 5;

        if(pad > 0){
            if(trunc_tail){
                i = 4 - ((3 - pad) * 8 + 5) / 6;
                p -= i;
            }
            for(i = 0; i < pad; i++){
                *p = pad_char;
                p++;
            }
        }
    }
    *p = '\0';
    return r;
}

char* base64_encode(const char* _Data, int _Size)
{
    return inner_base64_encode(_Data, _Size, g_base64_alphabet_table, TRUE);
}

char* base64_encode_url_safe(const char* _Data, int _Size)
{
    return inner_base64_encode(_Data, _Size, g_base64_alphabet_table2, TRUE);
}

#undef np
#define np 127
static const char g_base64_alphabet_value_table [256] = {
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  62,  np,  62,  np,  63,
         52,  53,  54,  55,  56,  57,  58,  59,
         60,  61,  np,  np,  np,  64,  np,  np,
         np,   0,   1,   2,   3,   4,   5,   6,
          7,   8,   9,  10,  11,  12,  13,  14,
         15,  16,  17,  18,  19,  20,  21,  22,
         23,  24,  25,  np,  np,  np,  np,  63,
         np,  26,  27,  28,  29,  30,  31,  32,
         33,  34,  35,  36,  37,  38,  39,  40,
         41,  42,  43,  44,  45,  46,  47,  48,
         49,  50,  51,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
};
#undef np

static int inner_base64_decode(const char* buf, int len, char* data, int* size, const char* value_table)
{
    /* 4 bytes => 3 bytes */
    int n = 0;

    if(!buf || len <=0 || !data || !size)
        return 0;

    char tmp[8] = {0}; /* 4 + 3 */
    unsigned long long w = 0; /* must at least 24 bit */
    unsigned char c = 0;
    int pad = 0;

    const unsigned char* q = (const unsigned char*)buf;
    char* p = data;
    int idx = 0;
    int i;
    int input_count = 0;
    int output_count = 0;
    int output_count2 = 0;
    while(idx < len)
    {
        input_count = 0;
        while(idx < len)
        {
            c = value_table[*q];
            idx++;
            q++;
            if(c < 64){
                if(pad){
                    break;
                }
                tmp[input_count++] = c;
                if(input_count >= 4){
                    break;
                }
            }else if(c == 64){
                pad++;
            }
        }
        if(input_count > 0){
            if(output_count){
                for(i = 0; i < output_count; i++)
                {
                    if(n >= *size){
                        break;
                    }
                    *p = tmp[4 + i];
                    p++;
                    n++;
                }
                output_count = 0;
                output_count2 = 0;
            }
            if(input_count < 4) {
                for(i = input_count; i < input_count; i++){
                    tmp[i] = 0;
                }
            }
            w = tmp[0] & 0x3F;
            w = (w << 6) | (tmp[1] & 0x3F);
            w = (w << 6) | (tmp[2] & 0x3F);
            w = (w << 6) | (tmp[3] & 0x3F);

            tmp[6] = w & 0xFF;
            w >>= 8;
            tmp[5] = w & 0xFF;
            w >>= 8;
            tmp[4] = w & 0xFF;

            output_count = 3;
            output_count2 = 3;

            if(input_count < 4) {
                output_count2 = input_count * 6 / 8;
            }

        }
        if(pad){
            if(output_count >= pad)
            {
                output_count -= pad;
                output_count2 = output_count;
            }
            break;
        }
    }
    if(output_count){
        if(output_count2 < output_count) output_count = output_count2;

        for(i = 0; i < output_count; i++)
        {
            if(n >= *size){
                break;
            }
            *p = tmp[4 + i];
            p++;
            n++;
        }
        output_count = 0;
    }
    *size = n;
    return n;
}

char* base64_decode(const char* _CodedStr, int _StrLen, char* _Data, int* _Size)
{
    if(!_CodedStr || _StrLen <=0)
        return NULL;

    char* p  = _Data;
    int size = 0;

    if(NULL == p){
        size = ((_StrLen + 3) / 4) * 3;
        p = malloc(size + 1);
        if(p){
            p[size] = '\0';
        }
    }else{
        if(_Size)
            size = *_Size;
    }

    int n = inner_base64_decode(_CodedStr, _StrLen, p, &size, g_base64_alphabet_value_table);

    if(_Size)
        *_Size = n;

    return p;
}

/* The RFC 4648 Base 32 alphabet */
char* g_base32_alphabet_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=";

static char* inner_base32_encode(const char* buf, int len, const char* alphabet_table, BOOL trunc_tail)
{
    /* 5 bytes => 8 bytes */
    char* r = NULL;

    if(!buf || len <=0)
        return NULL;

    int n = 1 + (len + 4) / 5 * 8 + (5 - len % 5) % 5;

    r = (char*) malloc(n);

    if(r == NULL)
        return NULL;

    r[n-1] = '\0';

    char pad_char = alphabet_table[32]; /* '=' */
    char tmp[8] = {0};

    unsigned long long w = 0; /* must at least 40 bit */

    const char* q = buf;
    char* p = r;
    int pad = 0;
    int idx = 0;
    int i;

    while(idx < len)
    {
        if(idx + 5 <= len){
            memcpy(tmp, q, 5);
            idx += 5;
            q += 5;
        }else{
            pad = idx + 5 - len;
            memcpy(tmp, q, len - idx);
            memset(tmp + 5 - pad, 0, pad);
            idx += 5;
        }

        w = (tmp[0] & 0xFF);
        w = (w << 8) | (tmp[1] & 0xFF);
        w = (w << 8) | (tmp[2] & 0xFF);
        w = (w << 8) | (tmp[3] & 0xFF);
        w = (w << 8) | (tmp[4] & 0xFF);

        p += 7;
        for(i = 0; i < 8; i++){
            *p = alphabet_table[w & 0x1F];
            p--;
            w >>= 5;
        }
        p += 9;

        if(pad > 0){
            if(trunc_tail){
                i = 8 - ((5 - pad) * 8 + 4) / 5;
                p -= i;
            }
            for(i = 0; i < pad; i++){
                *p = pad_char;
                p++;
            }
        }
    }
    *p = '\0';
    return r;
}

char* base32_encode(const char* _Data, int _Size)
{
    return inner_base32_encode(_Data, _Size, g_base32_alphabet_table, TRUE);
}

#undef np
#define np 127
static const char g_base32_alphabet_value_table [256] = {
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  26,  27,  28,  29,  30,  31,
         np,  np,  np,  np,  np,  32,  np,  np,
         np,   0,   1,   2,   3,   4,   5,   6,
          7,   8,   9,  10,  11,  12,  13,  14,
         15,  16,  17,  18,  19,  20,  21,  22,
         23,  24,  25,  np,  np,  np,  np,  np,
         np,   0,   1,   2,   3,   4,   5,   6,
          7,   8,   9,  10,  11,  12,  13,  14,
         15,  16,  17,  18,  19,  20,  21,  22,
         23,  24,  25,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
         np,  np,  np,  np,  np,  np,  np,  np,
 };
#undef np

static int inner_base32_decode(const char* buf, int len, char* data, int* size, const char* value_table)
{
    /* 8 bytes => 5 bytes */
    int n = 0;

    if(!buf || len <=0 || !data || !size)
        return 0;

    char tmp[16] = {0}; /* 8 + 5 */
    unsigned long long w = 0; /* must at least 40 bit */
    unsigned char c = 0;
    int pad = 0;

    const unsigned char* q = (const unsigned char*)buf;
    char* p = data;
    int idx = 0;
    int i;
    int input_count = 0;
    int output_count = 0;
    int output_count2 = 0;
    while(idx < len)
    {
        input_count = 0;
        while(idx < len)
        {
            c = value_table[*q];
            idx++;
            q++;
            if(c < 32){
                if(pad){
                    break;
                }
                tmp[input_count++] = c;
                if(input_count >= 8){
                    break;
                }
            }else if(c == 32){
                pad++;
            }
        }
        if(input_count > 0){
            if(output_count){
                for(i = 0; i < output_count; i++)
                {
                    if(n >= *size){
                        break;
                    }
                    *p = tmp[8 + i];
                    p++;
                    n++;
                }
                output_count = 0;
                output_count2 = 0;
            }
            if(input_count < 8) {
                for(i = input_count; i < input_count; i++){
                    tmp[i] = 0;
                }
            }
            w = tmp[0] & 0x1F;
            w = (w << 5) | (tmp[1] & 0x1F);
            w = (w << 5) | (tmp[2] & 0x1F);
            w = (w << 5) | (tmp[3] & 0x1F);
            w = (w << 5) | (tmp[4] & 0x1F);
            w = (w << 5) | (tmp[5] & 0x1F);
            w = (w << 5) | (tmp[6] & 0x1F);
            w = (w << 5) | (tmp[7] & 0x1F);

            tmp[12] = w & 0xFF;
            w >>= 8;
            tmp[11] = w & 0xFF;
            w >>= 8;
            tmp[10] = w & 0xFF;
            w >>= 8;
            tmp[9] = w & 0xFF;
            w >>= 8;
            tmp[8] = w & 0xFF;

            output_count = 5;
            output_count2 = 5;

            if(input_count < 8) {
                output_count2 = input_count * 5 / 8;
            }

        }
        if(pad){
            if(output_count >= pad)
            {
                output_count -= pad;
                output_count2 = output_count;
            }
            break;
        }
    }
    if(output_count){
        if(output_count2 < output_count) output_count = output_count2;

        for(i = 0; i < output_count; i++)
        {
            if(n >= *size){
                break;
            }
            *p = tmp[8 + i];
            p++;
            n++;
        }
        output_count = 0;
    }
    *size = n;
    return n;
}

char* base32_decode(const char* _CodedStr, int _StrLen, char* _Data, int* _Size)
{
    if(!_CodedStr || _StrLen <=0)
        return NULL;

    char* p  = _Data;
    int size = 0;

    if(NULL == p){
        size = ((_StrLen + 7) / 8) * 5;
        p = malloc(size + 1);
        if(p){
            p[size] = '\0';
        }
    }else{
        if(_Size)
            size = *_Size;
    }

    int n = inner_base32_decode(_CodedStr, _StrLen, p, &size, g_base32_alphabet_value_table);

    if(_Size)
        *_Size = n;

    return p;
}